package com.chaosj.skill.zipkin.proxy

import brave.Span
import brave.Tracer
import brave.Tracing
import com.chaosj.skill.zipkin.annotations.DelegateSpanParser
import com.chaosj.skill.zipkin.annotations.NewSpan
import com.chaosj.skill.zipkin.annotations.SpanEnum
import com.chaosj.skill.zipkin.context.ApplicationContext
import com.google.common.base.MoreObjects
import com.google.common.base.Strings

import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method

/**
 * @Description:
 * @author: caoyangjie
 * @date: 2019年12月2019/12/6日 09:38
 * @Copyright: © 赢时胜
 */
class ProxyInvocationHandler<T> implements InvocationHandler {
    private T delegate

    ProxyInvocationHandler(T delegate) {
        this.delegate = delegate
    }

    @Override
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object[] mergeArgs = Arrays.asList(delegate,method,args).toArray()
        Object result = null
        String log = ""
        boolean hasLog = false
        NewSpan yssSpan = method.getAnnotation(NewSpan.class)
        // 如果没有指定 链路追踪注解
        if( yssSpan==null ){
            return method.invoke(delegate,args)
        }
        Span span = null
        Span currentSpan = null
        try{
            Tracer tracer = ApplicationContext.getBean(Tracing.class).tracer()
            DelegateSpanParser parser = ApplicationContext.getBean(DelegateSpanParser.class)
            span = getSpan(yssSpan,currentSpan = tracer.currentSpan())
            span.name(parser.parserName(yssSpan, mergeArgs))
            log = parser.parserAnnotate(yssSpan, mergeArgs)
            parser.parserTag(yssSpan,mergeArgs,span)
            hasLog = Strings.isNullOrEmpty(log)
            if( hasLog ){
                logEvent(span,log+"->开始")
            }
            span.kind(Span.Kind.CLIENT)
            span.start()
            result = method.invoke(delegate,args)
        }catch(Exception e){
            span.error(e)
            if( hasLog ){
                logEvent(span,log+"->异常")
            }
            throw e
        }finally {
            if( hasLog ){
                logEvent(span,log+"->结束")
            }
            if(yssSpan.type() != NewSpan.SpanEnum.JOIN &&isNew(yssSpan,currentSpan)){
                span.finish()
            }
        }
        return result
    }

    private void logEvent(Span span, String name) {
        if (span != null) {
            span.annotate(name)
        }
    }

    protected final boolean isNew(NewSpan yssSpan,Span currentSpan){
        return currentSpan==null?true: currentSpan != getSpan(yssSpan, currentSpan)
    }

    protected final Span getSpan(NewSpan yssSpan, Span currentSpan){
        Tracer tracer = ApplicationContext.getBean(Tracing.class).tracer()
        Span span
        switch (yssSpan.type()){
            case SpanEnum.APPEND:
                span = MoreObjects.firstNonNull(currentSpan,tracer.nextSpan())
                break
            case SpanEnum.CREATE:
                span = tracer.nextSpan()
                break
            case SpanEnum.JOIN:
                span = tracer.joinSpan(MoreObjects.firstNonNull(currentSpan,tracer.nextSpan()).context())
                break
            default:
                span = MoreObjects.firstNonNull(currentSpan,tracer.nextSpan())
                break
        }
        return span
    }
}
